using System;
using System.IO;
// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
using System.Collections.Generic;
// Issue 10 - End
using System.Drawing;
using System.Drawing.Imaging;

namespace TheBox.UltimaImport
{
	/// <summary>
	/// Contains translation tables used for mapping body values to file subsets.
	/// <seealso cref="Animations" />
	/// </summary>
	public class BodyConverter
	{
		private static int[] m_Table1;
		private static int[] m_Table2;
		private static int[] m_Table3;

		private BodyConverter()
		{
		}

		static BodyConverter()
		{
			string path = ArtViewer.ArtViewer.MulManager[ "bodyconv.def", true ];

			if ( path == null )
				return;
			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			List<int> list1 = new List<int>(), list2 = new List<int>(), list3 = new List<int>();
			// Issue 10 - End
			int max1 = 0, max2 = 0, max3 = 0;

			using ( StreamReader ip = new StreamReader( path ) )
			{
				string line;

				while ( (line = ip.ReadLine()) != null )
				{
					if ( (line=line.Trim()).Length == 0 || line.StartsWith("#") )
						continue;

					try
					{
						string[] split = line.Split( '\t' );

						int original = System.Convert.ToInt32( split[0] );
						int anim2 = System.Convert.ToInt32( split[1] );
						int anim3;
						int anim4;

						try
						{
							anim3 = System.Convert.ToInt32( split[2] );
						}
						catch
						{
							anim3 = -1;
						}

						try
						{
							anim4 = System.Convert.ToInt32( split[ 3 ] );
						}
						catch
						{
							anim4 = -1;
						}

						if ( anim2 != -1 )
						{
							if ( anim2 == 68 )
								anim2 = 122;

							if ( original > max1 )
								max1 = original;

							list1.Add( original );
							list1.Add( anim2 );
						}

						if ( anim3 != -1 )
						{
							if ( original > max2 )
								max2 = original;

							list2.Add( original );
							list2.Add( anim3 );
						}

						if ( anim4 != -1 )
						{
							if ( original > max3 )
								max3 = original;

							list3.Add( original );
							list3.Add( anim4 );
						}
					}
					catch
					{
					}
				}
			}

			m_Table1 = new int[max1+1];

			for ( int i = 0; i < m_Table1.Length; ++i )
				m_Table1[i] = -1;

			for ( int i = 0; i < list1.Count; i += 2 )
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				m_Table1[list1[i]] = list1[i + 1];
				// Issue 10 - End

			m_Table2 = new int[max2+1];

			for ( int i = 0; i < m_Table2.Length; ++i )
				m_Table2[i] = -1;

			for ( int i = 0; i < list2.Count; i += 2 )
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				m_Table2[list2[i]] = list2[i + 1];
				// Issue 10 - End

			m_Table3 = new int[ max3 + 1 ];

			for ( int i = 0; i < m_Table3.Length; i++ )
				m_Table3[i] = -1;

			for ( int i = 0; i < list3.Count; i += 2 )
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				m_Table3[list3[i]] = list3[i+1];
				// Issue 10 - End
		}

		/// <summary>
		/// Checks to see if <paramref name="body" /> is contained within the mapping table.
		/// </summary>
		/// <returns>True if it is, false if not.</returns>
		public static bool Contains( int body )
		{
			if ( m_Table1 != null && body >= 0 && body < m_Table1.Length && m_Table1[body] != -1 )
				return true;

			if ( m_Table2 != null && body >= 0 && body < m_Table2.Length && m_Table2[body] != -1 )
				return true;

			if ( m_Table3 != null && body >= 0 && body < m_Table3.Length && m_Table3[body] != -1 )
				return true;

			return false;
		}

		/// <summary>
		/// Attempts to convert <paramref name="body" /> to a body index relative to a file subset, specified by the return value.
		/// </summary>
		/// <returns>A value indicating a file subset:
		/// <list type="table">
		/// <listheader>
		/// <term>Return Value</term>
		/// <description>File Subset</description>
		/// </listheader>
		/// <item>
		/// <term>1</term>
		/// <description>Anim.mul, Anim.idx (Standard)</description>
		/// </item>
		/// <item>
		/// <term>2</term>
		/// <description>Anim2.mul, Anim2.idx (LBR)</description>
		/// </item>
		/// <item>
		/// <term>3</term>
		/// <description>Anim3.mul, Anim3.idx (AOS)</description>
		/// </item>
		/// </list>
		/// </returns>
		public static int Convert( ref int body )
		{
			if ( m_Table1 != null && body >= 0 && body < m_Table1.Length )
			{
				int val = m_Table1[body];

				if ( val != -1 )
				{
					body = val;
					return 2;
				}
			}

			if ( m_Table2 != null && body >= 0 && body < m_Table2.Length )
			{
				int val = m_Table2[body];

				if ( val != -1 )
				{
					body = val;
					return 3;
				}
			}

			if ( m_Table3 != null && body >= 0 && body < m_Table3.Length )
			{
				int val = m_Table3[ body ];

				if ( val != -1 )
				{
					body = val;
					return 4;
				}
			}

			return 1;
		}
	}

	public class Animations
	{
		private static FileIndex m_FileIndex = new FileIndex( "Anim.idx", "Anim.mul", 0x40000, 6 );
		public static FileIndex FileIndex{ get{ return m_FileIndex; } }

		private static FileIndex m_FileIndex2 = new FileIndex( "Anim2.idx", "Anim2.mul", 0x10000, -1 );
		public static FileIndex FileIndex2{ get{ return m_FileIndex2; } }

		private static FileIndex m_FileIndex3 = new FileIndex( "Anim3.idx", "Anim3.mul", 0x20000, -1 );
		public static FileIndex FileIndex3{ get{ return m_FileIndex3; } }

		private static FileIndex m_FileIndex4 = new FileIndex( "Anim4.idx", "Anim4.mul", 0x10000, -1 );
		public static FileIndex FileIndex4{ get{ return m_FileIndex4; } }

		private Animations()
		{
		}

		public static Frame[] GetAnimation( int body, int action, int direction, int hue, bool preserveHue )
		{
			if ( preserveHue )
				Translate( ref body );
			else
				Translate( ref body, ref hue );

			int fileType = BodyConverter.Convert( ref body );
			FileIndex fileIndex;

			int index;

			switch ( fileType )
			{
				default:
				case 1:
				{
					fileIndex = m_FileIndex;

					if ( body < 200 )
						index = body * 110;
					else if ( body < 400 )
						index = 22000 + ((body - 200) * 65);
					else
						index = 35000 + ((body - 400) * 175);

					break;
				}
				case 2:
				{
					fileIndex = m_FileIndex2;

					if ( body < 200 )
						index = body * 110;
					else
						index = 22000 + ((body - 200) * 65);

					break;
				}
				case 3:
				{
					fileIndex = m_FileIndex3;

					if ( body < 300 )
						index = body * 65;
					else if ( body < 400 )
						index = 33000 + ((body - 300) * 110);
					else
						index = 35000 + ((body - 400) * 175);

					break;
				}
				case 4:
				{
					fileIndex = m_FileIndex4;

					if ( body < 200 )
						index = body * 110;
					else if ( body < 400 )
						index = 22000 + ((body - 200) * 65);
					else
						index = 35000 + ((body - 400) * 175);

					break;
				}
			}

			index += action * 5;

			if ( direction <= 4 )
				index += direction;
			else
				index += direction - (direction - 4) * 2;

			int length, extra;
			bool patched;
			Stream stream = fileIndex.Seek( index, out length, out extra, out patched );

			if ( stream == null )
				return null;

			bool flip = ( direction > 4 );

			BinaryReader bin = new BinaryReader( stream );

			ushort[] palette = new ushort[0x100];

			for ( int i = 0; i < 0x100; ++i )
				palette[i] = (ushort)(bin.ReadUInt16() ^ 0x8000);

			int start = (int)bin.BaseStream.Position;
			int frameCount = bin.ReadInt32();

			int[] lookups = new int[frameCount];

			for ( int i = 0; i < frameCount; ++i )
				lookups[i] = start + bin.ReadInt32();

			bool onlyHueGrayPixels = ( (hue & 0x8000) == 0 );

			hue = (hue & 0x3FFF) - 1;

			Hue hueObject = null;

			if ( hue > 0 && hue <= 3000 )
				hueObject = TheBox.UltimaImport.Hue.GetHue( hue ); // Zero based

			Frame[] frames = new Frame[frameCount];

			for ( int i = 0; i < frameCount; ++i )
			{
				bin.BaseStream.Seek( lookups[i], SeekOrigin.Begin );
				frames[i] = new Frame( palette, bin, flip );

				if ( hueObject != null )
					hueObject.ApplyTo( frames[i].Bitmap, false );
			}

			return frames;
		}

		private static int[] m_Table;

		public static void Translate( ref int body )
		{
			if ( m_Table == null )
				LoadTable();

			if ( body <= 0 || body >= m_Table.Length )
			{
				body = 0;
				return;
			}

			body = ( m_Table[body] & 0x7FFF );
		}

		public static void Translate( ref int body, ref int hue )
		{
			if ( m_Table == null )
				LoadTable();

			if ( body <= 0 || body >= m_Table.Length )
			{
				body = 0;
				return;
			}

			int table = m_Table[body];

			if ( (table & (1 << 31)) != 0 )
			{
				body = table & 0x7FFF;

				int vhue = (hue & 0x3FFF) - 1;

				if ( vhue < 0 || vhue >= 3000 )
					hue = (table >> 15) & 0xFFFF;
			}
		}

		private static void LoadTable()
		{
			int count = 400 + ((m_FileIndex.Index.Length - 35000) / 175);

			m_Table = new int[count];

			for ( int i = 0; i < count; ++i )
			{
				// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
				BodyTableEntry bte;
				BodyTable.m_Entries.TryGetValue(i, out bte);

				if ( bte == null || BodyConverter.Contains(i))
				// Issue 10 - End
				{
					m_Table[i] = i;
				}
				else
				{
					m_Table[i] = bte.m_OldID | (1 << 31) | (((bte.m_NewHue ^ 0x8000) & 0xFFFF) << 15);
				}
			}
		}
	}

	public class Frame
	{
		private Point m_Center;
		private Bitmap m_Bitmap;

		public Point Center{ get{ return m_Center; } }
		public Bitmap Bitmap{ get{ return m_Bitmap; } }

		private const int DoubleXor = (0x200 << 22) | (0x200 << 12);

		public static readonly Frame Empty = new Frame();
		public static readonly Frame[] EmptyFrames = new Frame[1]{ Empty };

		private Frame()
		{
			m_Bitmap = new Bitmap(1,1);
		}

		public unsafe Frame( ushort[] palette, BinaryReader bin, bool flip )
		{
			int xCenter = bin.ReadInt16();
			int yCenter = bin.ReadInt16();

			int width = bin.ReadUInt16();
			int height = bin.ReadUInt16();

			Bitmap bmp = new Bitmap( width, height, PixelFormat.Format16bppArgb1555 );
			BitmapData bd = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555 );

			ushort *line = (ushort *)bd.Scan0;
			int delta = bd.Stride >> 1;

			int header;

			int xBase = xCenter - 0x200;
			int yBase = (yCenter + height) - 0x200;

			if ( !flip )
			{
				line += xBase;
				line += (yBase * delta);

				while ( (header = bin.ReadInt32()) != 0x7FFF7FFF )
				{
					header ^= DoubleXor;

					ushort *cur = line + ((((header >> 12) & 0x3FF) * delta) + ((header >> 22) & 0x3FF));
					ushort *end = cur + (header & 0xFFF);

					while ( cur < end )
						*cur++ = palette[bin.ReadByte()];
				}
			}
			else
			{
				line -= xBase - width+1;
				line += (yBase * delta);

				while ( (header = bin.ReadInt32()) != 0x7FFF7FFF )
				{
					header ^= DoubleXor;

					ushort *cur = line + ((((header >> 12) & 0x3FF) * delta) - ((header >> 22) & 0x3FF));
					ushort *end = cur - (header & 0xFFF);

					while ( cur > end )
						*cur-- = palette[bin.ReadByte()];
				}

				xCenter = width-xCenter;
			}

			bmp.UnlockBits( bd );

			m_Center = new Point( xCenter, yCenter );
			m_Bitmap = bmp;
		}
	}

	public class BodyTableEntry
	{
		public int m_OldID;
		public int m_NewID;
		public int m_NewHue;

		public BodyTableEntry( int oldID, int newID, int newHue )
		{
			m_OldID = oldID;
			m_NewID = newID;
			m_NewHue = newHue;
		}
	}

	public class BodyTable
	{
		// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
		public static Dictionary<int, BodyTableEntry> m_Entries;
		// Issue 10 - End

		static BodyTable()
		{
			// Issue 10 - Update the code to Net Framework 3.5 - http://code.google.com/p/pandorasbox3/issues/detail?id=10 - Smjert
			m_Entries = new Dictionary<int, BodyTableEntry>();
			// Issue 10 - End

			string filePath = ArtViewer.ArtViewer.MulManager[ "body.def", true ];

			if ( filePath == null )
				return;

			StreamReader def = new StreamReader( filePath );

			string line;

			while ( (line = def.ReadLine()) != null )
			{
				if ( (line=line.Trim()).Length == 0 || line.StartsWith("#") )
					continue;

				try
				{
					int index1 = line.IndexOf( " {" );
					int index2 = line.IndexOf( "} " );

					string param1 = line.Substring( 0, index1 );
					string param2 = line.Substring( index1 + 2, index2 - index1 - 2 );
					string param3 = line.Substring( index2 + 2 );

					int indexOf = param2.IndexOf( ',' );

					if ( indexOf > -1 )
						param2 = param2.Substring( 0, indexOf ).Trim();

					int iParam1 = Convert.ToInt32( param1 );
					int iParam2 = Convert.ToInt32( param2 );
					int iParam3 = Convert.ToInt32( param3 );

					m_Entries[iParam1] = new BodyTableEntry( iParam2, iParam1, iParam3 );
				}
				catch
				{
				}
			}
		}
	}
}